package cn.tedu.sjmall.order.webapi.service.impl;

import cn.tedu.sjmall.commons.exception.ShengJiServiceException;
import cn.tedu.sjmall.commons.pojo.domain.SjmallAuthenticationInfo;
import cn.tedu.sjmall.commons.restful.JsonPage;
import cn.tedu.sjmall.commons.restful.ResponseCode;
import cn.tedu.sjmall.order.webapi.mapper.OmsOrderItemMapper;
import cn.tedu.sjmall.order.webapi.mapper.OmsOrderMapper;
import cn.tedu.sjmall.order.service.IOmsCartService;
import cn.tedu.sjmall.order.service.IOmsOrderService;
import cn.tedu.sjmall.order.webapi.utils.IdGeneratorUtils;
import cn.tedu.sjmall.pojo.order.dto.OrderAddDTO;
import cn.tedu.sjmall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.sjmall.pojo.order.dto.OrderListTimeDTO;
import cn.tedu.sjmall.pojo.order.dto.OrderStateUpdateDTO;
import cn.tedu.sjmall.pojo.order.model.OmsCart;
import cn.tedu.sjmall.pojo.order.model.OmsOrder;
import cn.tedu.sjmall.pojo.order.model.OmsOrderItem;
import cn.tedu.sjmall.pojo.order.vo.OrderAddVO;
import cn.tedu.sjmall.pojo.order.vo.OrderDetailVO;
import cn.tedu.sjmall.pojo.order.vo.OrderListVO;
import cn.tedu.sjmall.product.service.order.IForOrderSkuService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

// 订单管理模块业务逻辑层要声明为Dubbo的生产者,为后面秒杀业务提供服务
@DubboService
@Service
@Slf4j
public class OmsOrderServiceImpl implements IOmsOrderService {

    // Dubbo调用product模块减少库存的方法
    @DubboReference
    private IForOrderSkuService dubboSkuService;
    @Autowired
    private IOmsCartService omsCartService;
    @Autowired
    private OmsOrderMapper omsOrderMapper;
    @Autowired
    private OmsOrderItemMapper omsOrderItemMapper;

    // 新增订单的方法
    // 因为出现了dubbo调用其他模块(product)进行数据库操作
    // 所以需要启用分布式事务,使用seata保证数据完整性
    @GlobalTransactional
    @Override
    public OrderAddVO addOrder(OrderAddDTO orderAddDTO) {
        // 第一部分: 信息的收集
        // 先实例化订单,为订单收集信息
        OmsOrder order=new OmsOrder();
        // 将参数orderAddDTO同名属性赋值到order对象
        BeanUtils.copyProperties(orderAddDTO,order);
        // 经分析orderAddDTO中订单信息不完整,所以还需要将其它信息赋值到order对象
        // 我们专门编写一个方法来完成信息的补全
        loadOrder(order);
        // 到此为止,order对象的所有属性收集完毕
        // 下面开始为参数orderAddDTO中包含的订单项orderItems进行信息的收集
        List<OrderItemAddDTO> orderItems=orderAddDTO.getOrderItems();
        // 判断当前订单中包含的订单项集合是否为null
        if (orderItems == null || orderItems.isEmpty()){
            // 如果订单项集合中没有商品,抛出异常终止程序
            throw new ShengJiServiceException(
                    ResponseCode.BAD_REQUEST,"订单中至少要包含一件商品");
        }
        // 最终实现订单项集合新增到数据库的方法,参数要求是List<OmsOrderItem>类型
        // 但是参数传入的是List<OrderItemAddDTO>类型,需要实例化新的集合,并遍历进行转换
        List<OmsOrderItem> omsOrderItems=new ArrayList<>();
        // 开始遍历前端传入的订单参数集合,就是DTO集合
        for(OrderItemAddDTO addDTO : orderItems){
            // 需要转换,将addDTO转换为OmsOrderItem类型对象
            OmsOrderItem omsOrderItem=new OmsOrderItem();
            // 将addDTO同名属性赋值到omsOrderItem
            BeanUtils.copyProperties(addDTO,omsOrderItem);
            // 经过上面赋值,分析后得出需要给omsOrderItem对象的id和orderId属性赋值
            // id的值还是Leaf获得
            Long itemId=IdGeneratorUtils.getDistributeId("order_item");
            omsOrderItem.setId(itemId);
            // 从order对象中获取orderId
            omsOrderItem.setOrderId(order.getId());
            // 将已经赋值完成的omsOrderItem对象添加到omsOrderItems集合中,以备后续数据库操作
            omsOrderItems.add(omsOrderItem);
            // 第二部分: 数据库操作
            // 1.库存减少
            // 正在遍历的订单项对象中包含着要减少库存的skuId和要减少的数量
            // 先获取skuId
            Long skuId=omsOrderItem.getSkuId();
            // 减少库存是Dubbo的调用
            int rows=dubboSkuService.reduceStockNum(
                               skuId,omsOrderItem.getQuantity());
            if(rows==0){
                // 如果上面的修改操作,没有影响数据库的任何行,就是库存不足了
                log.error("商品库存不足,skuId:{}",skuId);
                // 抛出异常,终止程序,触发seata分布式事务回滚操作
                throw new ShengJiServiceException(
                        ResponseCode.BAD_REQUEST,"库存不足");
            }
            // 2.删除用户勾选的购物车中的商品
            OmsCart omsCart=new OmsCart();
            omsCart.setSkuId(skuId);
            omsCart.setUserId(order.getUserId());
            // 执行删除
            omsCartService.removeUserCarts(omsCart);
        }
        // 3.执行新增订单
        omsOrderMapper.insertOrder(order);
        // 4.执行新增订单项
        omsOrderItemMapper.insertOrderItemList(omsOrderItems);
        // 第三部分: 收集需要的返回值信息
        // 实例化返回值类型对象OrderAddVO
        OrderAddVO addVO=new OrderAddVO();
        addVO.setId(order.getId());
        addVO.setSn(order.getSn());
        addVO.setCreateTime(order.getGmtCreate());
        addVO.setPayAmount(order.getAmountOfActualPay());
        // 最后别忘了返回addVO
        return addVO;
    }
    // 为order对象进行赋值后信息补全的方法
    private void loadOrder(OmsOrder order) {
        // 先通过Leaf获取当前order订单的id
        Long id= IdGeneratorUtils.getDistributeId("order");
        order.setId(id);
        // 生成一个给用户的订单编号,也就是sn,生成UUID即可
        order.setSn(UUID.randomUUID().toString());
        // 从SpringSecurity中获取userid赋值给order对象
        // 以后的秒杀业务中,需要为秒杀userId赋值,所以当前的位置判断一下
        // 如果order对象的userId已经有值,就不需要赋值了
        if(order.getUserId()==null) {
            // 如果order的userId是null就给它赋值
            order.setUserId(getUserId());
        }
        // 下面可以将几个可能是null的属性,经过判断后赋默认值
        // 以state订单状态属性为例,其它的可以自行添加
        if(order.getState()==null){
            order.setState(0);
        }
        // 为了保证当前订单下单时间(gmt_order)和数据生成时间(gmt_create)相同
        // 我们为相关属性赋相同的值
        LocalDateTime now=LocalDateTime.now();
        order.setGmtOrder(now);
        order.setGmtCreate(now);
        order.setGmtModified(now);

        // 后端代码对前端传入的金额进行计算,进行验证
        // 实际支付金额公式:   原价-优惠+运费=实际支付金额
        // 因为要防止浮点偏移,所以计算都使用BigDecimal类型
        BigDecimal price=order.getAmountOfOriginalPrice();
        BigDecimal freight=order.getAmountOfFreight();
        BigDecimal discount=order.getAmountOfDiscount();
        // 进行计算
        BigDecimal actualPay=price.add(freight).subtract(discount);
        // 赋值到order中
        order.setAmountOfActualPay(actualPay);
    }

    // 根据订单id,修改订单状态
    @Override
    public void updateOrderState(OrderStateUpdateDTO orderStateUpdateDTO) {
        // 实例化OmsOrder对象
        OmsOrder order=new OmsOrder();
        // orderStateUpdateDTO只有id和state两个属性,直接同名属性赋值
        BeanUtils.copyProperties(orderStateUpdateDTO,order);
        // 执行调用动态修改订单的方法,因为order对象只有id和state,所以只能修改state
        omsOrderMapper.updateOrderById(order);
    }

    // 分页查询指定时间区间,当前登录用户所有订单信息
    @Override
    public JsonPage<OrderListVO> listOrdersBetweenTimes(OrderListTimeDTO orderListTimeDTO) {
        // 方法开始,需要先判断用户给定的时间是否可用
        // OrderListTimeDTO对象中,包含了开始和结束时间
        // 编写一个方法来进行判断和赋值
        validateTime(orderListTimeDTO);
        // 获取用户Id
        Long userId=getUserId();
        // 设置分页条件
        PageHelper.startPage(orderListTimeDTO.getPage(),
                orderListTimeDTO.getPageSize());
        // 执行查询
        List<OrderListVO> list=omsOrderMapper
                .selectOrderBetweenTimes(userId,
                        orderListTimeDTO.getStartTime(),
                        orderListTimeDTO.getEndTime());
        // 转换为JsonPage返回
        return JsonPage.restPage(new PageInfo<>(list));
    }

    // 验证时间正确性的方法,如果时间错误,默认最近一个月
    private void validateTime(OrderListTimeDTO orderListTimeDTO) {
        // 先取出开始和结束时间对象
        LocalDateTime start=orderListTimeDTO.getStartTime();
        LocalDateTime end=orderListTimeDTO.getEndTime();
        //  判断start和end是否有null,如果其中任何一个为null,就查询最近一个月订单
        if( start == null || end == null){
            // 开始时间设置为一个月前
            start=LocalDateTime.now().minusMonths(1);
            end=LocalDateTime.now();
            // 将设置好的值赋值到参数对象orderListTimeDTO中
            orderListTimeDTO.setStartTime(start);
            orderListTimeDTO.setEndTime(end);
        }else{
            // start和end都有值,要检查它们的先后关系,start要小于end
            // 如果end要是小于(早于)start
            // if(end.isBefore(start)){
            if(end.toInstant(ZoneOffset.of("+8")).toEpochMilli()<
                start.toInstant(ZoneOffset.of("+8")).toEpochMilli()){
                // 结束时间早于开始时间抛出异常
                throw new ShengJiServiceException(
                        ResponseCode.BAD_REQUEST,"结束时间应大于开始时间");
            }
        }


    }

    @Override
    public OrderDetailVO getOrderDetail(Long id) {
        return null;
    }

    public SjmallAuthenticationInfo getUserInfo(){
        // 获取SpringSecurity上下文对象
        UsernamePasswordAuthenticationToken token=
                (UsernamePasswordAuthenticationToken)
                        SecurityContextHolder.getContext().getAuthentication();
        // 为了程序逻辑严谨,判断一下token是否为null
        if(token == null){
            throw new ShengJiServiceException(
                    ResponseCode.UNAUTHORIZED,"您还没有登录");
        }
        // 从SpringSecurity上下文对象中获取用户信息
        SjmallAuthenticationInfo userInfo=
                (SjmallAuthenticationInfo) token.getCredentials();
        // 最终返回用户信息
        return userInfo;
    }
    public Long getUserId(){
        return getUserInfo().getId();
    }





}
